#pragma once
#ifndef MODEL_DISPLAYER_H
#define MODEL_DISPLAYER_H

#if defined(Viewer3DAPI_ModelDisplayer)
#  define Viewer3DAPI_EXPORT Q_DECL_EXPORT
#else
#  define Viewer3DAPI_EXPORT
#endif

#include <QOpenGLFunctions_3_3_Core>
#include <QtOpenGLWidgets/QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QFile>
#include <QTextStream>
#include <QMutex>

#include <type_traits>
#include <execution>

#include "ModelFileLoader.h"
#include "ModelViewer.h"
#include "ModelAttributesCalculator.h"

#define MAX_LIGHTS 10

enum DrawType_em
{
    PointCloud = GL_POINTS,
    Mesh = GL_TRIANGLES,
};

enum GrayScaleRange_em
{
    U8,
    U16,
};

enum LightMoveMode_em
{
    AsCamera,
    Fixed,
    Random,
    Period,
};

class Viewer3DAPI_EXPORT ModelDisplayer : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    ModelDisplayer(std::shared_ptr<IModelFileLoader> model_reader, std::shared_ptr<ViewCamera> view_camera);
    ~ModelDisplayer();

public:
    void applyRadialGrayscaleValues(GrayScaleRange_em gray_scale_range = GrayScaleRange_em::U16);
    void applyGlobalGrayscaleValues(GrayScaleRange_em gray_scale_range = GrayScaleRange_em::U16);
    void cancelGrayscaleConvertion();
    void cancelPureColorConvertion();
    void setModelViewPortMatched();
    void setModelViewPortFilled(int win_width, int win_height, ProjectionType projection_type);
    bool saveFramebufferToImage(const QString& filePath, const QString& imageFormation, int image_width, int image_height);
    void resizeModel(float scale_factor);

public slots:
    void setPureColor(const QVector3D& pure_color);
    void showProcessedPointCloud(const QVector<QVector<Vertex>>& pointCloud);

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;

private:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;
    // void keyPressEvent(QKeyEvent* event) override;

private:
    bool loadShaderSource(const QString &filePath, QString &source);
    bool compileAndLinkShaders(QOpenGLShaderProgram *p_program, const QString& vs_file_path, const QString& fs_file_path);
    void setModelShaderProgramDataFormation();
    void initFBO(int width, int height);
    void cleanupFBO();
    void setModelVertexData2Shader(const QVector<Vertex>& model_vertices, const QVector<unsigned int> &model_indices);
    void initMaterialTextures();
    GLuint loadTextureWithCache(const QString &filePath, const QByteArray &embeddedData);
    GLuint loadTextureWithCache(const QString &filePath, const QByteArray &embeddedData, QImage& out_texture_cpu);
    QImage loadEmbeddedTexture(const QByteArray &textureData);
    QImage loadExternalTexture(const QString &filePath);
    GLuint createGLTexture(const QImage &image);
    void calAndSetModelInitPhase(bool isFlip);
    void updateLightPos(LightMoveMode_em light_mode);
    void initColorMode();
    void obtainPointCloud();
    void obtainLighting();
    void setupLighting();
    void loadMaterialFromIndex(int material_index);
    template <ProjectionType ProjType>
    static std::pair<float, float> getProjectionCoords(const QMatrix4x4& V, const QMatrix4x4& M, const Vertex& v, float n)
    {
        auto vec_in_view = V * M * QVector4D(v.pos, 1.0f);
        if constexpr (ProjType == ProjectionType::Perspective) {
            float z = -vec_in_view.z();
            return { vec_in_view.x() * n / z, vec_in_view.y() * n / z };
        } else {
            return { vec_in_view.x(), vec_in_view.y() };
        }
    }

private:
    QMatrix4x4 m_model;
    std::shared_ptr<ViewCamera> m_sptr_camera;
    QVector<Light> m_lights;
    QVector3D m_ambientObj{1.0f, 1.0f, 1.0f}, m_diffObj{1.0f, 1.0f, 1.0f}, m_specObj{1.0f, 1.0f, 1.0f}; float m_shininess = 64.0f; // 物体相关反射

private:
    QOpenGLVertexArrayObject m_vao;
    QOpenGLBuffer m_vbo { QOpenGLBuffer::VertexBuffer };
    QOpenGLBuffer m_ebo { QOpenGLBuffer::IndexBuffer };
    GLuint m_fbo;
    GLuint m_fboTexture;
    GLuint m_rboDepth;
    QOpenGLShaderProgram *m_p_program = nullptr;
    int m_cur_draw_type = DrawType_em::Mesh;

private:
    QVector3D m_centroid;
    QVector3D m_initModelPhase;
    QVector<QVector<Vertex>> m_modelVertices;
    QVector<QVector<unsigned int>> m_modelIndices;
    QVector<Material> m_modelMaterials;
    QMap<QString, GLuint> m_textureCache; // 纹理缓存：key=纹理文件绝对路径，value=OpenGL纹理ID（避免重复加载）

private:
    QPoint m_lastMousePos; // 记录上一次鼠标位置
    bool m_isMousePressed = false; // 记录鼠标是否按下
    QVector3D m_rotationAxis = QVector3D(0.0f, 1.0f, 0.0f); // 旋转轴
    float m_rotationAngle = 0.0f; // 旋转角度

private:
    bool m_isMouseKeyLocked = false, m_isMouseWheelLocked = false, m_isKeyBoardLocked = false;
    bool m_isShowAxis = true, m_isGrayScaleMode = false, m_isPureColorMode = false;

private:
    std::shared_ptr<IModelFileLoader> m_sptr_modelLoader;
};

#endif // MODEL_DISPLAYER_H
